package hidra.many.metaheuristics.msops2;

import hidra.core.population.HIDRAPopulationAlgorithm;
import hidra.jmetal.core.Operator;
import hidra.jmetal.core.Problem;
import hidra.jmetal.core.Solution;
import hidra.jmetal.core.SolutionSet;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import jmetal.operators.selection.BinaryTournament;
import jmetal.util.Distance;
import jmetal.util.JMException;
import jmetal.util.archive.Archive;
import jmetal.util.comparators.SolutionComparator;

public class MSOPS2 extends HIDRAPopulationAlgorithm{

	int populationSize;
	SolutionSet population;

	Operator mutationOperator;
	Operator crossoverOperator;
	Operator selectionOperator;

	Distance distance = new Distance();
	SolutionComparator equalSolutions = new SolutionComparator();

	SolutionSet archive;

	private double[] refZ;
	private double[] scaleObj;

	private boolean dual = true;
	private int numTargets;
	private List<double[]> weights;




	public MSOPS2(Problem problem) {
		super(problem);
	}


	@Override
	protected SolutionSet initializationAlgorithm() throws ClassNotFoundException,JMException {
		initParams();
		Solution newSolution;
		for (int i = 0; i < populationSize; i++) {
			newSolution = new Solution(problem_);
			problem_.evaluate(newSolution);
			problem_.evaluateConstraints(newSolution);		
			population.add(newSolution);
		} 					
		return population;
	}


	protected void initParams() {
		super.initParams();
		populationSize = ((Integer) getInputParameter("populationSize")).intValue();
		population = new SolutionSet(populationSize);
		this.weights = new Vector<double[]>();
		mutationOperator = operators_.get("mutation");
		crossoverOperator = operators_.get("crossover");
		HashMap<String, Object> parameters = new HashMap<String, Object>();		
		selectionOperator = new BinaryTournament(parameters);

		refZ = new double[this.problem_.getNumberOfObjectives()];
		scaleObj = new double[this.problem_.getNumberOfObjectives()];
		archive = new Archive(100);
	}





	protected double[] updateRefZ(){		
		double[] refZ = new double[this.problem_.getNumberOfObjectives()];		
		for(int j=0; j < this.problem_.getNumberOfObjectives()  ;j++){				
			refZ[j] = Double.POSITIVE_INFINITY;
			for(int i=0; i < this.population.size(); i++){
				refZ[j] = Math.min(  refZ[j] , this.population.get(i).getObjective(j) );
			}

		}	
		return refZ;
	}


	private int numberFunctions = 2;

	protected double[] updateObjScale(){	

		double obj;
		double[] scaleObj = new double[this.problem_.getNumberOfObjectives()];	

		for(int k=0; k< numberFunctions; k++){					
			for(int j=0; j < this.problem_.getNumberOfObjectives()  ;j++){				
				scaleObj[j] = Double.NEGATIVE_INFINITY;
				for(int i=0; i < this.archive.size(); i++){
					obj = this.archive.get(i).getObjective(j);
					obj = obj - refZ[j];				
					scaleObj[j] = Math.max(scaleObj[j], obj); 				
				}

			}		
		}

		return scaleObj;

	}


	private List<Double[]> transformToUnitaryVector(List<Double[]> vectors){

		Double[] v;
		Double[] u;
		double norm =0.0;
		List<Double[]> units = new ArrayList<Double[]>();		

		for(int i=0; i < vectors.size() ;  i++){
			v = vectors.get(i);
			norm = this.norm(v);
			u = new Double[this.problem_.getNumberOfObjectives()];
			for(int j=0; j < v.length  ;  j++){
				u[i] = v[i]/norm;
			}
			units.add(u);
		}

		return units;
	}





	private double dotProduct(Double[] v1, Double[] v2){

		double inner = 0.0;

		for(int i=0; i < v1.length; i++){
			inner += (v1[i] * v2[i]);
		}

		return inner;
	}


	private double norm(Double[] v){

		double norm = 0.0;

		for(int i=0; i < v.length; i++){
			norm += (v[i] * v[i]);
		}

		return norm;

	}


	protected List<Double[]> scalingVectors( SolutionSet  vectors, double[] refZ, double[] scaleObj, int numObj){		
		List<Double[]> scaleVectors = new ArrayList<Double[]>();				
		for(int i=0;  i < vectors.size() ; i++){
			Solution v = vectors.get(i);
			Double[] u = new Double[v.numberOfObjectives()];
			for(int j=0; j  < numObj   ;j++){
				u[j] = (v.getObjective(j) - refZ[j]) / scaleObj[j];
			}		  
		}		
		return scaleVectors;	  
	}


	protected List<Double[]> scalingVectors( List<Double[]>  vectors, double[] refZ, double[] scaleObj, int numObj){		
		List<Double[]> scaleVectors = new ArrayList<Double[]>();				
		for(int i=0;  i < vectors.size() ; i++){
			Double[] v = vectors.get(i);
			Double[] u = new Double[v.length];
			for(int j=0; j  < numObj   ;j++){
				u[j] = (v[j] - refZ[j]) / scaleObj[j];
			}		  
		}		
		return scaleVectors;	  
	}


	@Override
	protected SolutionSet runIteration() throws JMException {		
		SolutionSet offspringPopulation;
		SolutionSet union;
		// Create the offSpring solutionSet      
		offspringPopulation = new SolutionSet(populationSize);
		Solution[] parents = new Solution[2];
		double[] refZ = updateRefZ();
		double[] scaleObj = updateObjScale();


		for(int j=0; j < this.problem_.getNumberOfObjectives() ; j++){

			if( refZ[j]  < this.refZ[j]){
				this.refZ[j] = refZ[j];
			}

			if(scaleObj[j] < this.scaleObj[j] ){
				this.scaleObj[j] = scaleObj[j];
			}

		}


		HashMap< Integer, List<Double> >   Rz = new HashMap< Integer, List<Double> >();
		HashMap< Integer, List<Double[]> > tvz = new HashMap< Integer, List<Double[]> >();


		List<Double[]> scaleVectors;
		List<Double[]> tv = null;
		List<Double> tempR = null;

		List<Double> R[] = new List[this.numberFunctions];


		for(int k=0; k <  this.numberFunctions ;k++){
			scaleVectors = this.scalingVectors(this.archive,this.refZ,this.scaleObj,this.problem_.getNumberOfObjectives());
			HashMap<String, Object>  out = aggregationFunctionWMM(scaleVectors);
			tempR   = (List<Double>) out.get("R");
			tv    = (List<Double[]>) out.get("tv");
			Rz.put(k, tempR);
			tvz.put(k, tv);
		}

		this.archive = this.population.union(this.archive);


		List<Double> m = null;


		boolean mvall[][] = new boolean[this.numberFunctions][this.archive.size()];

		for(int z=0; z < this.numberFunctions  ;z++){
			tempR = Rz.get(z);
			tv    = tvz.get(z);

			for(int i=0; i < this.populationSize; i++){			


				if(z == 0){
					scaleVectors = this.scalingVectors(this.archive,this.refZ,this.scaleObj,this.problem_.getNumberOfObjectives());
					HashMap<String, Object>  out = aggregationFunctionWMM(scaleVectors,tv.get(i));
					m = (List<Double>) out.get("m");

					double min = Double.POSITIVE_INFINITY;

					for(int j=0; j < m.size() ; j++){

						if(i != j)
							min = Math.min(min, m.get(j) );

					}

					if(min > tempR.get(i)){
						out = aggregationFunctionWMM(scaleVectors.get(i),tv);
						m = (List<Double>) out.get("m");

						for(int j=0; j < m.size() ; j++){

							if(m.get(j) > tempR.get(i)){

								mvall[z][j] = true;

							}

						}

						mvall[z][i] = false;
					}

				}

				if(z==1){

				}




			}			



		}




		for(int z=0; z  < this.numberFunctions ;   z++){		

			int j = 0;
			tempR = Rz.get(z);
			tv    = tvz.get(z);


			for(int i=0; i < mvall.length ;i++){

				if(mvall[z][i] == true){

					this.archive.remove(j);
					tempR.remove(j);
					tv.remove(j);

				}else{

					j++;

				}
			}

		}



		//prune the weigths


		scaleVectors = this.scalingVectors(this.archive,this.refZ,this.scaleObj,this.problem_.getNumberOfObjectives());
		List<Double[]> units = this.transformToUnitaryVector(scaleVectors);
		double nwt_max = 0;
		boolean scale_flag = true;

		List<Double[]> currentWeigths = new ArrayList<Double[]>();

		for(int i=0; i < units.size() ; i++){

			currentWeigths.add(units.get(i));

			if(currentWeigths.size() > nwt_max){


				if(scale_flag){
					double[] temRef = new double[this.problem_.getNumberOfObjectives()];
					List<Double[]> vectors = this.scalingVectors(currentWeigths,temRef,this.scaleObj,this.problem_.getNumberOfObjectives());
					pruneWeights(vectors,currentWeigths);

				}


			}


		}



		for(int z=0; z < this.numberFunctions ; z++){



		}


		for (int i = 0; i < (populationSize / 2); i++) {

			//obtain parents
			parents[0] = (Solution) selectionOperator.execute(population);
			parents[1] = (Solution) selectionOperator.execute(population);


			if(equalSolutions.compare(parents[0], parents[1]) == 0){				
				parents[1] = (Solution) selectionOperator.execute(population);
			}


			Solution[] offSpring = (Solution[]) crossoverOperator.execute(parents);
			mutationOperator.execute(offSpring[0]);
			mutationOperator.execute(offSpring[1]);
			problem_.evaluate(offSpring[0]);
			problem_.evaluateConstraints(offSpring[0]);
			problem_.evaluate(offSpring[1]);
			problem_.evaluateConstraints(offSpring[1]);
			offspringPopulation.add(offSpring[0]);
			offspringPopulation.add(offSpring[1]);
			//evaluations += 2;

		} // for

		// Create the solutionSet union of solutionSet and offSpring
		union =  population.union(offspringPopulation);



		population.clear();

		//Environmental Selection
		//union.sort(new MSOPSComparator(dual,numTargets));		

		int remain = populationSize; 


		while (  remain > 0  ) {
			//System.out.println("true"); 
			population.add(  union.get(0)  );
			union.remove(0);
			remain--;

		} // while


		iteration_++;
		//System.out.println(iteration_);

		//Ranking ranking = new Ranking(population);

		//return ranking.getSubfront(0);

		return population;
	}






	@Override
	protected SolutionSet getParetoFront() {		
		//Ranking ranking = new Ranking(population);
		//return ranking.getSubfront(0);
		return population;
	}



	private void pruneWeights(List<Double[]> vectors, List<Double[]>  currentWeigths){

		double min = Double.POSITIVE_INFINITY, temp = 0.0;
		int index = 0;

		for(int i=0; i < (vectors.size()-1)  ;i++){			
			for(int j=(i+1); j<vectors.size(); j++){				
				temp = this.dotProduct(vectors.get(i), vectors.get(j));				
				if(temp < min){
					min = temp;
					index= i;				
				}				
			}

		}

		vectors.remove(index);
		currentWeigths.remove(index);
	}



	//Aggragation Function Methods 	
	private HashMap aggregationFunctionWMM(List<Double[]> objs){


		HashMap<String, Object> out = new HashMap<String, Object>(); 

		List<Double[]> invObjs = new ArrayList<Double[]>();		
		List<Double[]> tv = transformToUnitaryVector(objs);
		List<Double> R = new ArrayList<Double>(objs.size());

		for(int i=0; i < objs.size() ;i++){
			Double[] v = objs.get(i);
			Double[] u = invertVector(v);
			invObjs.add(u);			
		}

		List<Double[]> tvx = transformToUnitaryVector(objs);


		for(int i=0; i < objs.size() ;i++){			
			double max = Double.NEGATIVE_INFINITY;
			Double[] v = objs.get(i);
			Double[] u = tvx.get(i);
			for(int j=0; j < this.problem_.getNumberOfObjectives() ;j++){
				double prod = (u[j] * v[j]);
				max = Math.max(prod, max);
			}
			R.add(max);
		}


		out.put("R", R);
		out.put("tv", tv);
		return out;
	}


	//Aggragation Function Methods 	
	private HashMap aggregationFunctionWMM(List<Double[]> objs, Double[] vectorRef){
		HashMap<String, Object> out = new HashMap<String, Object>(); 
		List<Double> m = new ArrayList<Double>(objs.size());

		for(int i=0; i < objs.size() ;i++){			
			double max = Double.NEGATIVE_INFINITY;
			Double[] v = objs.get(i);
			Double[] u = vectorRef;
			for(int j=0; j < this.problem_.getNumberOfObjectives() ;j++){
				double prod = (u[j] * v[j]);
				max = Math.max(prod, max);
			}
			m.add(max);
		}

		out.put("m", m);		
		return out;
	}


	//Aggragation Function Methods 	
	private HashMap aggregationFunctionWMM(Double[] obj, List<Double[]> weigths){
		HashMap<String, Object> out = new HashMap<String, Object>(); 
		List<Double> m = new ArrayList<Double>(weigths.size());

		for(int i=0; i < weigths.size() ;i++){			
			double max = Double.NEGATIVE_INFINITY;
			Double[] v = obj;
			Double[] u =  weigths.get(i);
			for(int j=0; j < this.problem_.getNumberOfObjectives() ;j++){
				double prod = (u[j] * v[j]);
				max = Math.max(prod, max);
			}
			m.add(max);
		}

		out.put("m", m);		
		return out;
	}



	private Double[] invertVector(Double[] u){		
		Double[] v = new Double[this.problem_.getNumberOfObjectives()];		
		for(int i=0; i<v.length; i++){
			v[i] = 1/u[i];
		}
		return v;
	}


}




